package com.ytrsoft.io;
import com.ytrsoft.bean.Action;

import java.io.IOException;
import java.io.InputStream;

public class IOInputStream extends InputStream {

    private byte[] input = new byte[100];

    private int inPointer = 0;

    private int outPointer = 0;

    private int size = 0;

    private boolean eofSent = false;

    private boolean closed = false;

    private IOInputStreamListener listener;

    public synchronized int read(byte[] b, int off, int len) throws IOException {
        waitForData();
        if (available() == -1) {
            return -1;
        }
        int toCopy = Math.min(len, size);
        if (input.length - outPointer > toCopy) {
            System.arraycopy(input, outPointer, b, off, toCopy);
            outPointer += toCopy;
        } else {
            int bytesToEnd = input.length - outPointer;
            System.arraycopy(input, outPointer, b, off, bytesToEnd);
            System.arraycopy(input, 0, b, off + bytesToEnd, toCopy - bytesToEnd);
            outPointer = toCopy - bytesToEnd;
        }
        size -= toCopy;
        return toCopy;
    }

    public int read(byte[] b) throws IOException {
        return read(b, 0, b.length);
    }

    public synchronized int read() throws IOException {
        waitForData();
        if (available() == -1) {
            return -1;
        }

        byte b = input[outPointer];
        outPointer++;
        if (outPointer == input.length) {
            outPointer = 0;
        }
        size -= 1;
        return b;
    }

    private void waitForData() {
        while (size == 0 && !closed) {
            try {
                notifyAction(Action.WAIT);
                wait();
            } catch (InterruptedException ignored) {}
        }
    }

    public synchronized void appendData(String text) {
        byte[] newData = text.getBytes();

        while (input.length - size < newData.length) {
            growArray();
        }

        if (size == 0) {
            System.arraycopy(newData, 0, input, 0, newData.length);
            inPointer = newData.length;
            size = newData.length;
            outPointer = 0;
        } else if (inPointer < outPointer || input.length - inPointer > newData.length) {
            System.arraycopy(newData, 0, input, inPointer, newData.length);
            inPointer += newData.length;
            size += newData.length;
        } else {
            System.arraycopy(newData, 0, input, inPointer, input.length - inPointer);
            System.arraycopy(newData, input.length - inPointer, input, 0, newData.length - (input.length - inPointer));
            inPointer = newData.length - (input.length - inPointer);
            size += newData.length;
        }

        if (inPointer == input.length) {
            inPointer = 0;
        }
        notifyAll();
        notifyAction(Action.NOTIFY);
    }

    private void growArray() {
        byte[] newInput = new byte[input.length + 1024];
        if (outPointer < inPointer) {
            System.arraycopy(input, outPointer, newInput, 0, size);
        } else {
            System.arraycopy(input, outPointer, newInput, 0, input.length - outPointer);
            System.arraycopy(input, 0, newInput, input.length - outPointer, inPointer);
        }
        outPointer = 0;
        inPointer = size;
        input = newInput;
        newInput = null;
    }

    public int available() throws IOException {
        if (closed && eofSent) {
            throw new IOException("Input Stream Closed");
        } else if (size == 0) {
            if (!eofSent) {
                eofSent = true;
                return -1;
            }
            throw new IOException("Input Stream Closed");
        }
        return size;
    }

    public synchronized void close() throws IOException {
        if (closed) {
            throw new IOException("Input Stream Closed");
        }
        closed = true;
        notifyAll();
    }

    public void setOnIOInputStreamListener(IOInputStreamListener listener) {
        this.listener = listener;
    }

    private void notifyAction(Action action) {
        if (this.listener != null) {
            this.listener.input(action);
        }
    }

}

